/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.game;

import com.serponix.game.food.Cake;
import com.serponix.game.food.Cherry;
import com.serponix.game.food.Food;
import com.serponix.game.food.TypPotraviny;
import com.serponix.game.objektyNaPlose.VecNaPolicku;
import com.serponix.game.objektyNaPlose.ViceObjektu;
import com.serponix.game.projectiles.Projectile;
import com.serponix.game.score.Score;
import com.serponix.gui.WindowInterface;
import com.serponix.net.Informace;
import com.serponix.net.Net;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Random;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.Timer;

/**
 * Model celé hry. Obsahuje všechny hady, potraviny a objekty ve hře.
 * Má dvourozměrné pole všech políček ve hře, kam lze uložit věc na ploše, nebo ji z ní odebrat. Lze se také zeptat, na jakém políčku co leží.
 * Poskytuje metody pro krok hry, která by měla pohybovat hady a všemi objekty, které to vyžadují.
 *
 * @author Daniel Vala
 */
public abstract class GameModel extends Observable {

	private WindowInterface okno;
	protected Net net;
	private Timer timer;
	private Uloha uloha;
	private boolean pauza;
	private Mapa mapa;
	public Cake jidlo;
	public Cherry bonus;
	protected Snake[] hadi;
	private List<Player> hraci;
	private int pocetHadu;
	private JLabel[] skoreStitky;
	private MessagePanel panelZprav;
	private GamingArea herniPanel;
	private int sirkaHernihoPaneluLogicka;
	private int vyskaHernihoPaneluLogicka;
	private int sirkaHernihoPaneluProVzhled;
	private int vyskaHernihoPaneluProVzhled;
	protected final Random generatorNahody;
	protected List<Integer> listPocatecnichPozic;
	private VecNaPolicku[][] veciNaPlose;
	private static final int velikostPolicka;
	private int casStitku = 0;
	protected Color[] barvy = { Color.GREEN, Color.BLUE, Color.RED, Color.YELLOW, Color.ORANGE, Color.MAGENTA, Color.PINK, Color.BLACK, Color.CYAN, Color.LIGHT_GRAY };
	public boolean ladeni = true;
	public boolean zobrazVeciNaPlose;
	public boolean krokovani;
	protected List<Projectile> strely;
	protected boolean newRound;

	static {
		velikostPolicka = Consts.SIZE_OF_BOX;
	}

	public GameModel(WindowInterface okno, GameParameters parametryHry, GamingArea herniPanel, JLabel[] skoreStitky, MessagePanel panelZprav, Dimension velikostHernihoPanelu) throws IllegalArgumentException {
		if (velikostHernihoPanelu.getWidth() < 100) {
			throw new IllegalArgumentException("Šírka herního panelu nesmí být menší než 100.");
		}
		this.okno = okno;
		this.herniPanel = herniPanel;
		this.skoreStitky = skoreStitky;
		this.pocetHadu = parametryHry.getNumberOfPlayers();
		this.hraci = parametryHry.getPlayers();
		sirkaHernihoPaneluProVzhled = (int) velikostHernihoPanelu.getWidth();
		vyskaHernihoPaneluProVzhled = (int) velikostHernihoPanelu.getHeight();
		this.panelZprav = panelZprav;
		this.mapa = parametryHry.getMap();
		generatorNahody = new Random();
		sirkaHernihoPaneluLogicka = sirkaHernihoPaneluProVzhled / getVelikostPolicka();
		vyskaHernihoPaneluLogicka = vyskaHernihoPaneluProVzhled / getVelikostPolicka();
		veciNaPlose = new VecNaPolicku[sirkaHernihoPaneluProVzhled][vyskaHernihoPaneluLogicka];
		mapa.namapujCelouMapu(this);
		strely = new ArrayList<Projectile>();

		// pokud je tento model modelem client, nevytvoří se timer
		if (!parametryHry.getMode().equals(GameModeEnum.CLIENT)) {
			uloha = new Uloha(this);
			timer = new Timer(parametryHry.getSpeed(), uloha);
		}
	}

	public void addStrela(Projectile strela) {
		strely.add(strela);
	}

	protected void odstranVsechnyStrely() {
		for (Projectile strela : strely) {
			strela.odmapuj(this);
		}
		strely.clear();
	}

	/**
	 * Definuje způsob vykreslení všech zdí mapy.
	 *
	 * @param g Grafický objekt pro kreslení.
	 */
	abstract void paintZdi(Graphics g);

	/**
	 * Definuje, jak se bude resetovat zadaný had.
	 *
	 * @param had Had, který bude resetován.
	 */
	abstract void resetHada(Snake had);

	/**
	 * Definuje, jak nastavit všechny hady do počáteční pozice.
	 */
	public abstract void newRound();

	abstract void jidloSezrano(Snake had);

	abstract void bonusSezran(Snake had);

	/**
	 * Definuje, co se stane za jeden krok hry.
	 */
	abstract void krok();

	/**
	 * Definuje, co se vykreslí na hrací plochu.
	 *
	 * @param g
	 */
	abstract void paint(Graphics g);

	/**
	 * Definuje, co se stane při přidání daného počtu bodů do zadaného skóre.
	 *
	 * @param skore Skóre, kam se přidají nové body.
	 * @param body  Počet bodů, které se přidají.
	 */
	abstract void addScore(Score skore, int body);

	/**
	 * Odstartuje hru zapnutím timeru na zadanou rychlost z parametrů hry.
	 */
	protected void startHry() {
		timer.start();
	}

	public Point getPolickoPred(int x, int y, Direction smer) {
		switch (smer) {
			case UP:
				y--;
				break;
			case DOWN:
				y++;
				break;
			case LEFT:
				x--;
				break;
			case RIGHT:
				x++;
				break;
		}
		return new Point(x, y);
	}

	public void umistiVecNaHerniPlochu(VecNaPolicku novaVecNaPlose, int x, int y) {
		if (isVHerniMape(x, y)) {
			VecNaPolicku staraVecNaPlose = veciNaPlose[x][y];
			// pokud na požadovaném místě leží už jiná věc na ploše, nová se jen přidá
			if (staraVecNaPlose != null) {
				// pokud na požadovaném místě leží více věcí na ploše, tato se přidá
				if (staraVecNaPlose instanceof ViceObjektu) {
					ViceObjektu viceObjektu = (ViceObjektu) staraVecNaPlose;
					viceObjektu.addVec(novaVecNaPlose);
					// pokud na požadovaném místě leží právě jedna věc na ploše,
					// vytvoří se instance více objektů, kde se předá stará i nová věc
				} else {
					ViceObjektu viceObjektu = new ViceObjektu(staraVecNaPlose, novaVecNaPlose);
					veciNaPlose[x][y] = viceObjektu;
				}
				// pokud na požadovaném místě neleží žádná věc na ploše, jednoduše se tam přidá nová věc na ploše
			} else {
				veciNaPlose[x][y] = novaVecNaPlose;
			}
		}
	}

	public boolean odstranVecNaHerniPlose(VecNaPolicku vecNaPloseKOdstraneni) {
		int x = vecNaPloseKOdstraneni.getX();
		int y = vecNaPloseKOdstraneni.getY();
		// pokud chci odmapovávat v herní ploše a něco je zde skutečně namapováno
		if (isVHerniMape(x, y) && veciNaPlose[x][y] != null) {
			// pokud je na místě, kde je věc na odmapování, ještě jiná věc, odmapuje se jen požadovaná
			if (veciNaPlose[x][y] instanceof ViceObjektu) {
				ViceObjektu viceObjektu = (ViceObjektu) veciNaPlose[x][y];
				boolean removed = viceObjektu.removeVec(vecNaPloseKOdstraneni);
				// pokud po odmapování požadované věci zbývá na souřadnicích už jen jedna věc,
				// zruší se instance více objektů a do modelu se předá jen tato jedna věc
				if (viceObjektu.getPocetVeci() == 1) {
					veciNaPlose[x][y] = viceObjektu.getVecNaPlose(0);
				}
				return removed;
				// pokud je na daném místě jen jedna věc a to ta, kterou chci odmapovat
			} else if (vecNaPloseKOdstraneni.equals(veciNaPlose[x][y])) {
				veciNaPlose[x][y] = null;
				return true;
			}
		}
		return false;
	}

	public VecNaPolicku getObjektNaHerniPlose(int x, int y) {
		if (isVHerniMape(x, y)) {
			return veciNaPlose[x][y];
		} else {
			return null;
		}
	}

	public void zobrazVeciNaPlose(Graphics g) {
		Graphics2D g2d = (Graphics2D) g;
		for (int i = 0; i < veciNaPlose.length; i++) {
			for (int j = 0; j < veciNaPlose[i].length; j++) {
				if (veciNaPlose[i][j] != null) {
					if (veciNaPlose[i][j] instanceof ViceObjektu) {
						g2d.setPaint(Color.RED);
					} else {
						g2d.setPaint(Color.BLUE);
					}
					g2d.fillOval(i * velikostPolicka, j * velikostPolicka, velikostPolicka, velikostPolicka);
				}
			}
		}
	}

	public boolean isVHerniMape(int x, int y) {
		if (x >= 0 && y >= 0 && x < getSirkaLogicka() && y < getVyskaLogicka()) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Pošle zadaná data všem klientům.
	 *
	 * @param data Data k poslání.
	 */
	public void posliDataKlientum(String data) {
		net.poslatData(data);
	}

	public Cake getJidlo() {
		return jidlo;
	}

	public Cherry getBonus() {
		return bonus;
	}

	public Food getPotravina(TypPotraviny typPotraviny) {
		if (typPotraviny == TypPotraviny.JIDLO) {
			return jidlo;
		} else if (typPotraviny == TypPotraviny.BONUS) {
			return bonus;
		} else {
			return null;
		}
	}

	/**
	 * Ukončí hru a pošle všem klientům tuto informaci.
	 *
	 * @param zpravaOVitezstvi Zpráva, kterou model zobrazí a kterou pošle klientům.
	 */
	public void ukonciHruVitezstvim(String zpravaOVitezstvi) {
		net.poslatData(Informace.VITEZSTVI + Informace.ODDELOVAC1 + zpravaOVitezstvi);
		JOptionPane.showMessageDialog(null, zpravaOVitezstvi, "KONEC HRY", JOptionPane.INFORMATION_MESSAGE);
		ukonciHru();
	}

	/**
	 * Ukončí hru a zobrazí menu.
	 */
	public void ukonciHru() {
		if (net != null) {
			net.odpojSe();
		}
		zastavHru();
		okno.displayMenu();
	}

	/**
	 * Vrátí mapu hry.
	 *
	 * @return Mapa
	 */
	public Mapa getMapa() {
		return mapa;
	}

	public int getSirkaLogicka() {
		return sirkaHernihoPaneluLogicka;
	}

	public int getVyskaLogicka() {
		return vyskaHernihoPaneluLogicka;
	}

	/**
	 * Vrátí šířku herního panelu.
	 *
	 * @return Šířka herního panelu.
	 */
	public int getSirkaProVzhled() {
		return sirkaHernihoPaneluProVzhled;
	}

	/**
	 * Vrátí výšku herního panelu.
	 *
	 * @return Výška herního panelu.
	 */
	public int getVyskaProVzhled() {
		return vyskaHernihoPaneluProVzhled;
	}

	/**
	 * Vrátí celkový počet hadů, živých i mrtvých.
	 *
	 * @return Počet všech hadů.
	 */
	public int getPocetHadu() {
		return pocetHadu;
	}

	/**
	 * Vrátí velikost políčka - čtverečku.
	 *
	 * @return Rozměr strany čtvercového políčka herní plochy.
	 */
	public static int getVelikostPolicka() {
		return velikostPolicka;
	}

	public Player getHrac(int cisloHrace) {
		return hraci.get(cisloHrace);
	}

	public String getSkoreText(int cisloStitku) {
		return skoreStitky[cisloStitku].getText();
	}

	/**
	 * Nastaví zadaný text na daný štítek.
	 * Pokud je šítků více než 5, nemuselo by být pro všechny místo,
	 * proto se zobrazí pouze sudé nebo liché štítky v závislosti na čase.
	 * Čas je závislý na rychlosti hry.
	 *
	 * @param cisloStitku Číslo šítku, který se nastavý na daný text.
	 * @param text        Text, který se nastaví na zadaný štítek.
	 */
	public void setSkoreText(int cisloStitku, String text) {
		if (pocetHadu <= 5) {
			skoreStitky[cisloStitku].setText(text);
		} else {
			if (casStitku < 900) {
				if (cisloStitku % 2 == 0) {
					skoreStitky[cisloStitku].setText(text);
				} else {
					skoreStitky[cisloStitku].setText("");
				}
			} else {
				if (cisloStitku % 2 == 1) {
					skoreStitky[cisloStitku].setText(text);
				} else {
					skoreStitky[cisloStitku].setText("");
				}
			}
			casStitku++;
			if (casStitku >= 1800) {
				casStitku = 0;
			}
		}
	}

	public void setAllSkoreText(int cisloStitku, String text) {
		skoreStitky[cisloStitku].setText(text);
	}

	public MessagePanel getPanelZprav() {
		return panelZprav;
	}

	public GamingArea getHerniPanel() {
		return herniPanel;
	}

	protected void vytvorListPocatecnichPozic(int pocetPozic) {
		if (getSirkaLogicka() < 10) {
			throw new RuntimeException("Logická šířka herního panelu nesmí být menší než 10.");
		}
		listPocatecnichPozic = new ArrayList<Integer>(10);
		int zaklad = getSirkaLogicka() / pocetPozic;
		for (int i = 0; i < pocetPozic; i++) {
			listPocatecnichPozic.add(zaklad * i);
		}
	}

	/**
	 * Pokud zbyl už jen jediný had naživu, stane se vítězem.
	 *
	 * @return <tt> true </tt> pokud se jediný zbylý had stal vítězem.
	 */
	protected boolean isWinner() {
		Snake posledni = null;
		for (int i = 0; i < getPocetHadu(); i++) {
			if (hadi[i].isAlive()) {
				if (posledni == null) {
					posledni = hadi[i];
				} else {
					return false; // pokud se najde druhý živý had, nic se nestane
				}
			}
		}
		if (posledni != null) {
			posledni.score.vyhra();
			return true;
		} else {
			System.err.println("CHYBA! Všichni hadi jsou mrtví");
			return false;
		}
	}

	/**
	 * Zjistí, zda je ve hře živý hráč, nebo hrají už jen umělé inteligence.
	 *
	 * @return True, pokud hraje živý hráč. False, pokud hrají jen AI.
	 */
	public boolean isHumanPlaying() {
		for (Player hrac : hraci) {
			if (hrac.getAI() == null && hrac.getSnake().isAlive()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Oživne všechny hady.
	 */
	public void ozivVsechnyHady() {
		for (int i = 0; i < getPocetHadu(); i++) {
			hadi[i].oziv();
		}
	}

	/**
	 * Zkontroluje, pokud je daný had blízko některé z krajních stěn.
	 *
	 * @param had
	 * @return směr, kterým by se had měl vydat, aby se stěně vyhnul (proti hodinovým ručičkám).
	 */
	public Direction vyhniSeStenam(Snake had) {
		if (had.getX() < 20) {
			return Direction.LEFT;
		}

		if (had.getY() < 20) {
			return Direction.UP;
		}

		if (Math.abs(had.getX() - getSirkaProVzhled()) < 20) {
			return Direction.RIGHT;
		}

		if (Math.abs(had.getY() - getVyskaProVzhled()) < 20) {
			return Direction.RIGHT;
		}

		return null;
	}

	/**
	 * Pozastaví hru na zadaný počet milisekund.
	 *
	 * @param cas Čas v milisekundách.
	 */
	public void pockej(int cas) {
		timer.setInitialDelay(cas);
		timer.restart();
		timer.setInitialDelay(0);
	}

	/**
	 * Zastaví hru zrušením timeru, pokud existuje.
	 */
	public void zastavHru() {
		if (timer != null) {
			timer.stop();
		}
	}

	/**
	 * Opětovně spustí celou hru
	 */
	public void pokracuj() {
		timer.start();
	}

	/**
	 * Zastaví nebo opět spustí běh celé hry.
	 */
	public void pauza() {
		if (!pauza) {
			zastavHru();
			pauza = true;
		} else {
			pokracuj();
			pauza = false;
		}
	}

	//    public void zrychli() {
	//        timer.cancel();
	//        timer = new Timer();
	//        uloha = new Uloha(this);
	//        timer.scheduleAtFixedRate(uloha, 0, parametryHry.getRychlost() - 10);
	//    }
	//
	//    public void zpomal() {
	//        timer.cancel();
	//        timer = new Timer();
	//        uloha = new Uloha(this);
	//        timer.scheduleAtFixedRate(uloha, 0, parametryHry.getRychlost() + 10);
	//    }
	public int getPocetZivychHadu() {
		int ziviHadi = 0;
		for (int i = 0; i < getPocetHadu(); i++) {
			if (hadi[i].isAlive()) {
				ziviHadi++;
			}
		}
		return ziviHadi;
	}

	public List<Snake> getZiviHadi() {
		List<Snake> ziviHadi = new ArrayList<Snake>();
		for (int i = 0; i < getPocetHadu(); i++) {
			if (hadi[i].isAlive()) {
				ziviHadi.add(hadi[i]);
			}
		}
		return ziviHadi;
	}

	/**
	 * Vrátí pole všech hadů ve hře.
	 *
	 * @return Pole všech hadů.
	 */
	public Snake[] getHadi() {
		Snake[] retval = new Snake[hraci.size()];
		for (int i = 0; i < retval.length; i++) {
			retval[i] = hraci.get(i).getSnake();
		}
		return retval;
	}
}
